package eval.utilities.random;

import cern.jet.random.AbstractDistribution;
import eval.utilities.configuration.ExperimentConfiguration;

/**
 * Class implementing an alternating probabilistic distribution Values generated
 * by this class alternates between two periods: low and high.
 * 
 * The duration of each period is probabilistically fixed through a Poisson
 * random variable.
 * 
 * @author Nicolo' M. Calcavecchia <calcavecchia@elet.polimi.it>
 * @author Amit Peled   <amit.kapina@gmail.com>
 * @author Igal Kaufman <igal.kaufman@gmail.com>
 */
public class AlternatingDistribution extends AbstractDistribution {

	private ExperimentConfiguration conf = ExperimentConfiguration
			.getSingleton();

	private static final long serialVersionUID = 1L;

	private boolean isLowPeriod;
	private int periodDuration;
	private int periodCounter = 0;

	// distributions for time
	private AbstractDistribution lowPeriodDistribution;
	private AbstractDistribution highPeriodDistribution;

	// distributions for demand
	private AbstractDistribution lowPeriodDemandDistribution;
	private AbstractDistribution highPeriodDemandDistribution;

	// the demand of VM low
	private int lowPeriodAverageDemand;
	// the demand of VM at high
	private int highPeriodAverageDemand;

	/**
	 * 
	 * @Constructor
	 * @param lowPeriodAverageDuration the number of tick that VM at low 
	 * @param lowPeriodAverageDemand the demand of VM at low
	 * @param highPeriodAverageDuration the number of tick that VM at high 
	 * @param highPeriodAverageDemand  the demand of VM at high
	 */
	public AlternatingDistribution(int lowPeriodAverageDuration, int lowPeriodAverageDemand, 
			int highPeriodAverageDuration, int highPeriodAverageDemand) {
		RandomUtilities tmp = new RandomUtilities();
		//the demand 
		switch (ProbabilisticDistributionsEnum
				.valueOf(conf.demandDistribution)) {
				case CONSTANT:
					// low period data
					this.lowPeriodDemandDistribution = tmp
					.getNewConstantDistribution(lowPeriodAverageDemand);
					this.lowPeriodAverageDemand = lowPeriodAverageDemand;
					// high period data
					this.highPeriodDemandDistribution = tmp
							.getNewConstantDistribution(highPeriodAverageDemand);
					this.highPeriodAverageDemand = highPeriodAverageDemand;
					break;
				case NORMAL:
					// low period data
					this.lowPeriodDemandDistribution = tmp
					.getNewNormalDistribution(lowPeriodAverageDemand,lowPeriodAverageDemand * conf.demandDistributionSTD);
					this.lowPeriodAverageDemand = lowPeriodAverageDemand;
					// high period data
					this.highPeriodDemandDistribution = tmp
							.getNewNormalDistribution(highPeriodAverageDemand,highPeriodAverageDemand *  conf.demandDistributionSTD);
					this.highPeriodAverageDemand = highPeriodAverageDemand;
					break;
				case POISSON:
					// low period data
					this.lowPeriodDemandDistribution = tmp
					.getNewPoissonDistribution(lowPeriodAverageDemand);
					this.lowPeriodAverageDemand = lowPeriodAverageDemand;
					// high period data
					this.highPeriodDemandDistribution = tmp
							.getNewPoissonDistribution(highPeriodAverageDemand);
					this.highPeriodAverageDemand = highPeriodAverageDemand;
					break;
				default:
					throw new IllegalArgumentException(
							"The requested distribution for high/low Period Average Duration is not implemented");

		}
		//the distribution 
		switch (ProbabilisticDistributionsEnum
				.valueOf(conf.PeriodAverageDurationDistribution)) {

				case CONSTANT:
					// low period data
					this.lowPeriodDistribution = tmp
					.getNewConstantDistribution(lowPeriodAverageDuration);
					// high period data
					this.highPeriodDistribution = tmp
							.getNewConstantDistribution(highPeriodAverageDuration);
					break;
				case NORMAL:
					// low period data
					this.lowPeriodDistribution = tmp
					.getNewNormalDistribution(lowPeriodAverageDuration, lowPeriodAverageDuration * 0.2);
					// high period data
					this.highPeriodDistribution = tmp
							.getNewNormalDistribution(lowPeriodAverageDuration, lowPeriodAverageDuration * 0.2);
					break;
				case POISSON:
					// low period data
					this.lowPeriodDistribution = tmp
					.getNewPoissonDistribution(lowPeriodAverageDuration);
					// high period data
					this.highPeriodDistribution = tmp
							.getNewPoissonDistribution(lowPeriodAverageDuration);
					break;
				default:
					throw new IllegalArgumentException(
							"The requested distribution for high/low Period Average Duration is not implemented");

		}
		init(lowPeriodAverageDuration, highPeriodAverageDuration);
	}

	/**
	 * start with low period
	 * 
	 * @param lowPeriodAverageDuration
	 * @param highPeriodAverageDuration
	 */
	private void init(int lowPeriodAverageDuration,
			int highPeriodAverageDuration) {
		// start with low period
		isLowPeriod = true;
		periodDuration = lowPeriodDistribution.nextInt();
	}

	/* (non-Javadoc)
	 * @see cern.jet.random.AbstractDistribution#nextDouble()
	 */
	@Override
	public double nextDouble() {
		periodCounter++;

		double demand = isLowPeriod ? lowPeriodDemandDistribution.nextInt() : highPeriodDemandDistribution.nextInt();

		if (periodCounter >= periodDuration) {
			isLowPeriod = !isLowPeriod;
			periodCounter = 0;
			periodDuration = (isLowPeriod) ? lowPeriodDistribution.nextInt()
					: highPeriodDistribution.nextInt();
		}
		return demand;
	}

	/* (non-Javadoc)
	 * @see cern.jet.random.AbstractDistribution#nextInt()
	 */
	@Override
	public int nextInt() {
		periodCounter++;

		int demand = isLowPeriod ? lowPeriodDemandDistribution.nextInt() : highPeriodDemandDistribution.nextInt();

		if (periodCounter >= periodDuration) {
			isLowPeriod = !isLowPeriod;
			periodCounter = 0;
			periodDuration = (isLowPeriod) ? lowPeriodDistribution.nextInt()
					: highPeriodDistribution.nextInt();
		}

		return demand;
	}

	/**
	 * @param initializationValue
	 */
	public void setInitValue(int initializationValue) {
		this.periodCounter = initializationValue % periodDuration;
	}
}
